/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package tetris.model;

import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
import java.awt.Font;

public class BoardModel extends Model{

    private CellModel[][] board;
    private int boardXSize;
    private int boardYSize;
    private int score;
    private int timerTracker;
    private Piece currentBlock;
    private Piece nextBlock;
    private Boolean blockFalling = false;
    private Boolean gameIsOver = false;

    public BoardModel(int x_size, int y_size) {
        this.boardXSize = x_size;
        this.boardYSize = y_size;
        this.clearBoard();
    }

    public boolean increaseTimer(){
        if(this.timerTracker >= 10){
            this.timerTracker = 0;
            return true;
        }else{
            return false;
        }
    }
    public void dropBlock(){
        int x = this.currentBlock.getX();
        int y = this.currentBlock.getY();
        if(this.gameIsOver){
            return;
        }
        while(true){
            try {
                this.board[y][x].unoccupy();

                this.board[y + this.currentBlock.getPieceOne().getY()]
                        [x + this.currentBlock.getPieceOne().getX()].unoccupy();

                this.board[y + this.currentBlock.getPieceTwo().getY()]
                        [x + this.currentBlock.getPieceTwo().getX()].unoccupy();

                this.board[y + this.currentBlock.getPieceThree().getY()]
                        [x + this.currentBlock.getPieceThree().getX()].unoccupy();
                y++;

                this.currentBlock.setXY(x, y);
                this.board[y][x].occupied();

                this.board[y + this.currentBlock.getPieceOne().getY()]
                        [x + this.currentBlock.getPieceOne().getX()].occupied();

                this.board[y + this.currentBlock.getPieceTwo().getY()]
                        [x + this.currentBlock.getPieceTwo().getX()].occupied();

                this.board[y + this.currentBlock.getPieceThree().getY()]
                        [x + this.currentBlock.getPieceThree().getX()].occupied();
                }catch(Exception e){
                    // Collision
                    y--;
                    this.currentBlock.setXY(x, y);
                    this.board[y][x].occupy(this.currentBlock.getColor());

                    this.board[y + this.currentBlock.getPieceOne().getY()]
                            [x + this.currentBlock.getPieceOne().getX()].occupy(this.currentBlock.getColor());

                    this.board[y + this.currentBlock.getPieceTwo().getY()]
                            [x + this.currentBlock.getPieceTwo().getX()].occupy(this.currentBlock.getColor());

                    this.board[y + this.currentBlock.getPieceThree().getY()]
                            [x + this.currentBlock.getPieceThree().getX()].occupy(this.currentBlock.getColor());
                    this.blockFalling = false;
                    break;
                  }
        }
    }

    public void rotateBlock(){
        this.blockFalling = true;
        int x = this.currentBlock.getX();
        int y = this.currentBlock.getY();

       this.board[y][x].unoccupy();

       this.board[y + this.currentBlock.getPieceOne().getY()]
                [x + this.currentBlock.getPieceOne().getX()].unoccupy();

       this.board[y + this.currentBlock.getPieceTwo().getY()]
                [x + this.currentBlock.getPieceTwo().getX()].unoccupy();

       this.board[y + this.currentBlock.getPieceThree().getY()]
                [x + this.currentBlock.getPieceThree().getX()].unoccupy();

       this.currentBlock.rotate();

        if (collision(this.currentBlock)){
            this.currentBlock.rotateBack();
            this.board[y][x].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceOne().getY()]
                    [x + this.currentBlock.getPieceOne().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceTwo().getY()]
                    [x + this.currentBlock.getPieceTwo().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceThree().getY()]
                    [x + this.currentBlock.getPieceThree().getX()].occupy(this.currentBlock.getColor());
        }else{
            x = this.currentBlock.getX();
            y = this.currentBlock.getY();
            this.board[y][x].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceOne().getY()]
                    [x + this.currentBlock.getPieceOne().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceTwo().getY()]
                    [x + this.currentBlock.getPieceTwo().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceThree().getY()]
                    [x + this.currentBlock.getPieceThree().getX()].occupy(this.currentBlock.getColor());
        }
    }

    public void boardGravitation(){
        int stepper = this.boardYSize-1;
        int nextNonEmptyLine = 0;
        while(true){
            if(lineIsEmpty(stepper)){
                nextNonEmptyLine = getNextNonEmptyLine(stepper);
                if(nextNonEmptyLine == 0){
                    break;
                }
                copyLine(nextNonEmptyLine, stepper);
                unoccupyLine(nextNonEmptyLine);
            }
            stepper--;
        }
    }

    private boolean lineIsEmpty(int lineNumber){
        for(int i=0; i < this.boardXSize; i++){
            if(this.board[lineNumber][i].isOccupied()){
                return false;
            }
        }
        return true;
    }

    private int getNextNonEmptyLine(int lineNumber){
        if(lineNumber < 2){
            this.gameIsOver = true;
            return 0;
        }
        try {
            for(int i=lineNumber-1; i > lineNumber-5; i--){
                if(!lineIsEmpty(i)){
                    return i;
                }
            }
            return 0;
        }catch(Exception e){
            this.gameIsOver = true;
            return 0;
        }
    }
   
    /*
     * removeFullLines:  Sets all full lines to unoccupied.
     *                   and returns the number of lines removed.
     */
    public int removeFullLines(){
        Boolean full = false;
        int numberOfLinesRemoved = 0;
        for(int i= this.boardYSize-1; i > 0; i--){
            full = true;
            for(int j=0; j < this.boardXSize; j++){
                if(!this.board[i][j].isOccupied()){
                    full = false;
                }
            }
            if(full){
                unoccupyLine(i);
                numberOfLinesRemoved++;
            }
        }
        increaseScore(numberOfLinesRemoved);
        return numberOfLinesRemoved;
    }

    public void step(){
        if(!blockFalling){
            removeFullLines();
            if(!lineIsEmpty(3)){
                this.gameIsOver = true;
                return;
            }
            boardGravitation();
            insertNewBlock();
            /*currentBlock = nextBlock;
            currentBlock.setActive((int)this.boardXSize/2, 3);
            nextBlock = randomPiece();
            blockFalling = true;*/
        }else {
            gravitation();
        }
    }
    /*
     * insertNewBlock:  Tries to insert a new block, if it collides
     *                  the game is over.
     */
    private void insertNewBlock(){
        Piece testPiece = nextBlock;
        testPiece.setActive((int)(this.boardXSize/2)-1, 3);
        if(collision(testPiece)){
            this.gameIsOver = true;
            return;
        }else{
            currentBlock = nextBlock;
            currentBlock.setActive((int)(this.boardXSize/2)-1, 3);
            nextBlock = randomPiece();
            blockFalling = true;
        }
    }
    public void clearBoard(){
        this.board = new CellModel[this.boardYSize][this.boardXSize];
        for(int i = 0;i < boardYSize; i++){
            for(int j=0;j < boardXSize; j++){
                this.board[i][j] = new CellModel();
            }
        }
    }

    public void startBoard(){
        int x = (int)(this.boardXSize/2)-1;
        int y = 2;
        this.currentBlock = randomPiece();
        this.blockFalling = true;
        this.nextBlock = randomPiece();

        this.currentBlock.setActive(x, y);
        try {
            this.board[y][x].occupy(this.currentBlock.color);
            this.board[y + this.currentBlock.getPieceOne().getY()]
                    [x + this.currentBlock.getPieceOne().getX()].occupy(this.currentBlock.color);

            this.board[y + this.currentBlock.getPieceTwo().getY()]
                    [x + this.currentBlock.getPieceTwo().getX()].occupy(this.currentBlock.color);

            this.board[y + this.currentBlock.getPieceThree().getY()]
                    [x + this.currentBlock.getPieceThree().getX()].occupy(this.currentBlock.color);
        }catch(Exception e){
            System.out.println("startBoard Error");
            System.exit(0);
        }
    }
    /*
     * copyLine: Copies line one to line two.
     *
     * @param lineOne the line to be copied.
     * @param lineTwo the line to copy to.
     */
    private void copyLine(int lineOne, int lineTwo){
       for(int i=0; i < this.boardXSize; i++){
           if(this.board[lineOne][i].isOccupied()){
               this.board[lineTwo][i].occupy(this.board[lineOne][i].getColor());
           }else {
               this.board[lineTwo][i].unoccupy();
           }
       }
    }

    private void increaseScore(int linesRemoved){
        this.score += linesRemoved*linesRemoved;
        this.timerTracker += linesRemoved*linesRemoved;
    }

    private void unoccupyLine(int lineNumber){
        for(int i=0; i < this.boardXSize; i++){
            this.board[lineNumber][i].unoccupy();
        }
    }
    
    private void gravitation(){
        int x = this.currentBlock.getX();
        int y = this.currentBlock.getY();

        this.board[y][x].unoccupy();

        this.board[y + this.currentBlock.getPieceOne().getY()]
                [x + this.currentBlock.getPieceOne().getX()].unoccupy();

        this.board[y + this.currentBlock.getPieceTwo().getY()]
                [x + this.currentBlock.getPieceTwo().getX()].unoccupy();

        this.board[y + this.currentBlock.getPieceThree().getY()]
                [x + this.currentBlock.getPieceThree().getX()].unoccupy();
        y++;

        this.currentBlock.setXY(x, y);
        try {
            this.board[y][x].occupied();

            this.board[y + this.currentBlock.getPieceOne().getY()]
                    [x + this.currentBlock.getPieceOne().getX()].occupied();

            this.board[y + this.currentBlock.getPieceTwo().getY()]
                    [x + this.currentBlock.getPieceTwo().getX()].occupied();

            this.board[y + this.currentBlock.getPieceThree().getY()]
                    [x + this.currentBlock.getPieceThree().getX()].occupied();
        }catch(Exception e){
            // Collision
            y--;
            this.currentBlock.setXY(x, y);
            this.board[y][x].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceOne().getY()]
                    [x + this.currentBlock.getPieceOne().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceTwo().getY()]
                    [x + this.currentBlock.getPieceTwo().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceThree().getY()]
                    [x + this.currentBlock.getPieceThree().getX()].occupy(this.currentBlock.getColor());
            this.blockFalling = false;
            return;
        }
       // No Collisions, occupy again.
            this.board[y][x].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceOne().getY()]
                    [x + this.currentBlock.getPieceOne().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceTwo().getY()]
                    [x + this.currentBlock.getPieceTwo().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceThree().getY()]
                    [x + this.currentBlock.getPieceThree().getX()].occupy(this.currentBlock.getColor());
            return;
    }

    public boolean collision(Piece piece){
       int x = piece.getX();
       int y = piece.getY();

       try {
            this.board[y][x].occupied();

            this.board[y + piece.getPieceOne().getY()]
                    [x + piece.getPieceOne().getX()].occupied();

            this.board[y + piece.getPieceTwo().getY()]
                    [x + piece.getPieceTwo().getX()].occupied();

            this.board[y + piece.getPieceThree().getY()]
                    [x + piece.getPieceThree().getX()].occupied();
        }catch(Exception e){
            return true;
        }
        return false;
    }

    public void move(int direction){
        //it should not stick if you move it.
        this.blockFalling = true;
        int x = this.currentBlock.getX();
        int y = this.currentBlock.getY();
        int originalX = x;
        
        this.board[y][x].unoccupy();

        this.board[y + this.currentBlock.getPieceOne().getY()]
                [x + this.currentBlock.getPieceOne().getX()].unoccupy();

        this.board[y + this.currentBlock.getPieceTwo().getY()]
                [x + this.currentBlock.getPieceTwo().getX()].unoccupy();

        this.board[y + this.currentBlock.getPieceThree().getY()]
                [x + this.currentBlock.getPieceThree().getX()].unoccupy();
        if(direction == 0){
            x--;
        }else{
            x++;
        }

        this.currentBlock.setXY(x, y);
        try {
            this.board[y][x].occupied();

            this.board[y + this.currentBlock.getPieceOne().getY()]
                    [x + this.currentBlock.getPieceOne().getX()].occupied();

            this.board[y + this.currentBlock.getPieceTwo().getY()]
                    [x + this.currentBlock.getPieceTwo().getX()].occupied();

            this.board[y + this.currentBlock.getPieceThree().getY()]
                    [x + this.currentBlock.getPieceThree().getX()].occupied();
        }catch(Exception e){
            // Collision
            x = originalX;
            this.currentBlock.setXY(x, y);
            this.board[y][x].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceOne().getY()]
                    [x + this.currentBlock.getPieceOne().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceTwo().getY()]
                    [x + this.currentBlock.getPieceTwo().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceThree().getY()]
                    [x + this.currentBlock.getPieceThree().getX()].occupy(this.currentBlock.getColor());
            //this.blockFalling = false;
            return;
        }
       // No Collisions, occupy.
            this.board[y][x].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceOne().getY()]
                    [x + this.currentBlock.getPieceOne().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceTwo().getY()]
                    [x + this.currentBlock.getPieceTwo().getX()].occupy(this.currentBlock.getColor());

            this.board[y + this.currentBlock.getPieceThree().getY()]
                    [x + this.currentBlock.getPieceThree().getX()].occupy(this.currentBlock.getColor());
            return;
    }

    public boolean isGameOver(){
        return this.gameIsOver;
    }
    public void paintBoard(Graphics g){
        // painting the game board.
        for(int i=2; i < this.boardYSize; i++){
            for(int j=0;j < this.boardXSize; j++){
                this.board[i][j].paintCell(i, j, g);
            }
        }
        paintNextBlock(g);
        paintScore(1, this.boardYSize*20+1*20, g);
        if(this.gameIsOver){
            paintGameIsOver(g);
        }
    }

    public void paintGameIsOver(Graphics g){
        int x = (int)(this.boardXSize/4)*20;
        int y = (int)(this.boardYSize/4)*20+20;
        g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 40));
        g.setColor(Color.WHITE);
        g.drawString("GAME", x, y);
        g.drawString("OVER", x, y+40);
    }

    private void paintNextBlock(Graphics g){
        int x = this.boardXSize*20+20;
        int y = 6*20;
        g.setColor(Color.WHITE);
        g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 18));
        g.drawString("Next Block", x, y-20);
        g.setColor(Color.BLACK);
        g.fillRect(x, y, 120, 120);
        this.nextBlock.paintPiece(x+2*20, y+2*20, g);
    }

    private void paintScore(int x, int y, Graphics g){
        g.setColor(Color.WHITE);
         g.setColor(Color.WHITE);
        g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 18));
        g.drawString("Score: "+ Integer.toString(this.score), x, y);
    }

    private Piece randomPiece(){
        Piece newPiece;
        Random r = new Random();
        int random = r.nextInt(6);
        switch(random){
            case 0:
                newPiece = new PieceI();
                break;
            case 1:
                newPiece = new PieceJ();
                break;
            case 2:
                newPiece = new PieceL();
                break;
            case 3:
                newPiece = new PieceO();
                break;
            case 4:
                newPiece = new PieceS();
                break;
            case 5:
                newPiece = new PieceT();
                break;
            case 6:
                newPiece = new PieceZ();
                break;
            default:
                newPiece = new PieceZ();

        }
        return newPiece;
    }
}